#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"

const std::string oper = "+-*/%";

// C:计算
// S:储存
template <class C, class S>
class BlockQueues
{
public:
    BolockQueue<C> *c_bq;
    BolockQueue<S> *s_bq;
};

int mymath(int x, int y, char op)
{
    switch (op)
    {
    case '+':
        return x + y;
        break;
    case '-':
        return x - y;
        break;
    case '*':
        return x * y;
        break;
    case '/':
        if (y == 0)
        {
            std::cout << "div zero error!" << std::endl;
            return -1;
        }
        else
            return x / y;
        break;
    case '%':
        if (y == 0)
        {
            std::cout << "mod zero error!" << std::endl;
            return -1;
        }
        else
            return x % y;
        break;
    default:
        std::cout << "输入非法" << std::endl;
        break;
    }
}

void *consumer(void *_bq)
{
    BolockQueue<calTask> *bq = (static_cast<BlockQueues<calTask, saveTask> *>(_bq))->c_bq;
    BolockQueue<saveTask> *save_bq = (static_cast<BlockQueues<calTask, saveTask> *>(_bq))->s_bq;
    while (true)
    {
        // 消费活动
        calTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout << "cal thread 完成消费任务： " << result << "done..." << std::endl;
        saveTask save(result, Save);
        save_bq->push(save);
        std::cout << "cla thread 完成推送保存任务..." << std::endl;
    }
    return nullptr;
}
void *productor(void *_bq)
{
    BolockQueue<calTask> *bq = (static_cast<BlockQueues<calTask, saveTask> *>(_bq))->c_bq;
    // BolockQueue<Task> *bq = static_cast<BolockQueue<Task> *>(_bq);

    while (true)
    {

        // 生产活动
        int x = rand() % 10 + 1; // 1-10
        int y = rand() % 10 + 1; // 1-10
        int operCode = rand() % oper.size();
        calTask t(x, y, oper[operCode], mymath);
        bq->push(t);
        std::cout << "pro thread 完成生产任务： " << t.toTaskString() << std::endl;
    }
    return nullptr;
}

void *saver(void *_bq)
{
    BolockQueue<saveTask> *save_bq = (static_cast<BlockQueues<calTask, saveTask> *>(_bq))->s_bq;
    while (true)
    {
        saveTask t;
        save_bq->pop(&t);
        t();
        std::cout << "save thread 完成保存任务..." << std::endl;
    }
    return nullptr;
}

int main()
{
    srand((unsigned long)time(0));

    BlockQueues<calTask, saveTask> bqs;
    bqs.c_bq = new BolockQueue<calTask>();  // 想把临界资源传递进线程参数 用于生产和消费
    bqs.s_bq = new BolockQueue<saveTask>(); // 用于消费后保存到文件
    pthread_t c[2], p[3], s[2];
    pthread_create(p, nullptr, productor, &bqs);
    pthread_create(p+1, nullptr, productor, &bqs);
    pthread_create(p+2, nullptr, productor, &bqs);
    pthread_create(c, nullptr, consumer, &bqs);
    pthread_create(c+1, nullptr, consumer, &bqs);

    pthread_create(s, nullptr, saver, &bqs);
    pthread_create(s+1, nullptr, saver, &bqs);

    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);
    pthread_join(s[0], nullptr);
    pthread_join(s[1], nullptr);

    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

///////////////////////////////////////基于BlockingQueue的生产者消费者模型/////////////////////////////////

// 就是多了个条件变量！
// 在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别
// 在于，当队列为空时，从队列获取元素的操作将会被阻塞，直到队列中被放入了元素；当队列满时，往队列里存放元
// 素的操作也会被阻塞，直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的，线程在对阻塞队列进程
// 操作时会被阻塞)

// 生产着与消费者之间多了个缓冲区，使他俩完成解耦，不解耦--消费者直接向生产者拿数据
// 缓冲器 -- 共享资源(多执行流访问，那就可能出现问题)

// 生产者和生产者之间：互斥关系（我要放数据，你也要放数据）
// 消费者和消费者之间：互斥关系（我要拿数据，你也要拿数据）
// 生产者和消费者之间：同步 && 互斥 （同步：我不用主动问生产者有数据没，而是让缓冲器拿到生产者数据了，来通知我）
// 怕缓冲区满了，生产者一直访问临界资源，导致消费者拿不到锁 --> 条件变量
// 总结"321"原则
// 3种关系
// 2种角色
// 1个交易场所

// 挖掘特点：
// 完成生产者和消费者的解耦
// 支持生产者和消费者的一段时间的忙先不均的为不同(因为有缓冲器)
// 提高效率(生产者只用负责生产，消费者只用消费)
// 高效在哪里？？ 生产活动可能是冲外设，网络等拿来了，构造任务要花时间，消费者也要花时间做任务，其他消费者也能拿出来，并行执行

///////////////////////////////////////Linux线程同步 && 条件变量 //////////////////////////////////////////

// 线程同步 -- 解锁后(如果是互斥，优先级高，能立刻拿到锁，引发其他线程的饥饿问题！)不能立刻申请锁，要重新排队 -- 按照顺序访问临界资源
// 条件变量 -- 防止线程访问临界资源时，临界资源不足，还一直加锁-访问临界资源-解锁 -> 造成其他线程不能访问临界资源(因为拿不到锁)
// 1.当条件不满足的时候，我们线程必须区某些定义好的条件变量上进行等待！等待队列！

// int pthread_cond_destroy(pthread_cond_t *cond);
// int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
// pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 当条件不满足时，让线程等待
// int pthread_cond_broadcast(pthread_cond_t *cond); 唤醒一批线程 broadcast：广播
// int pthread_cond_signal(pthread_cond_t *cond); 唤醒一个线程
